#pragma once

#include <iostream>
#include <string>
#include <mutex>
#include <cassert>
#include <fstream>
#include <jsoncpp/json/json.h>
#include <algorithm>
#include "../../comm/util.hpp"
#include "../../comm/log.hpp"
// #include "oj_model_file.hpp"
#include "oj_model_mysql.hpp"
#include "oj_view.hpp"
#include "../../comm/httplib.h"

namespace ns_control
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    //提供编译运行服务的主机
    class Machine
    {
    public:
        std::string _ip;      //编译运行服务ip
        int _port;            //编译运行服务端口
        uint64_t _load;       //编译运行服务负载
        std::mutex *_pmutex;  // mutex禁止拷贝使用指针
    public:
        Machine():_ip(""),_port(0),_load(0),_pmutex(nullptr)
        {}
        ~Machine()
        {}
        

        //负载的改变需要加锁
        //主机负载提升
        void LoadIncrease()
        {
            if(_pmutex) _pmutex->lock();
            ++_load;
            if(_pmutex) _pmutex->unlock();
        }
        //主机负载减少
        void LoadDecrease()
        {
            if(_pmutex) _pmutex->lock();
            --_load;
            if(_pmutex) _pmutex->unlock();
        }

        //主机负载清0
        void LoadReset()
        {
            if(_pmutex) _pmutex->lock();
            _load = 0;
            if(_pmutex) _pmutex->unlock();
        }
    };

    //负载均衡模块:多请求并发访问负载均衡模块，加锁保护
    const std::string machine_service_conf = "./conf/machine_service.conf";
    class LoadBalance
    {
    private:
        //为我们提供编译运行服务的主机
        std::vector<Machine> _machines;//使用vector进行管理，可以使用下标代替相应主机id

        std::vector<int> _online;//所有在线主机的id
        std::vector<int> _offline;//所有离线主机的id
        std::mutex _mutex;
    public:
        LoadBalance()
        {
            assert(LoadConf(machine_service_conf));
        }
        ~LoadBalance()
        {}
        
        //加载提供编译运行服务主机的配置文件
        bool LoadConf(const std::string &machine_list)
        {
            std::ifstream in(machine_service_conf);
            if(!in.is_open())
            {
                LOG(LogLevel::FATAL) << "加载: " << machine_service_conf << " 失败";
                return false;
            }
            std::string line;
            //按行进行切分
            while(std::getline(in,line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line,tokens,":");
                if(tokens.size() != 2)
                {
                    LOG(LogLevel::WARNING) << "切分" << line << "失败";
                    continue;
                }
                Machine m;
                m._ip = tokens[0];
                m._port = atoi(tokens[1].c_str());
                m._load = 0;
                m._pmutex = new std::mutex();//指针

                _online.push_back(_machines.size());//插入到在线主机中:下标位置表示Id
                _machines.push_back(m);//插入到主机vector中
            }
            LOG(LogLevel::INFO) << "加载所有编译运行主机成功";
            return true;
        }

        //智能选择：进行负载均衡
        bool SmartChoice(int& id,Machine** m)
        {
            //1.使用主机,更新主机负载
            //2.可能需要离线该主机

            _mutex.lock();
            //负载均衡算法
            //1.随机数+hash
            //2.轮询+hash
            int online_num = _online.size();
            if(online_num == 0)
            {
                LOG(LogLevel::FATAL) << "所有的后端编译运行主机已经离线";
                _mutex.unlock();//此处务必要释放锁
                return false;
            }
            //存在在线主机,进行挑选
            
            //遍历方式，找到当前最小的负载
            id = _online[0];
            *m = &_machines[_online[0]]; 
            uint64_t min_load = _machines[_online[0]]._load;
            for(int i = 1;i < online_num;i++)
            {
                if(min_load > _machines[_online[i]]._load)
                {
                    min_load = _machines[_online[i]]._load;
                    id = _online[i];
                    *m = &_machines[_online[i]];                            
                }
            }
            _mutex.unlock();
            return true;
        }

        void OfflineMachine(int id)
        {
            _mutex.lock();//加锁,离线主机和智能选择:并发安全
            for(auto iter = _online.begin();iter != _online.end();iter++)
            {
                if(*iter == id)
                {
                    _online.erase(iter);
                    _offline.push_back(id);//这里不能写*iter,避免迭代器失效

                    //离线对应主机时,需要将其负载清0,否则再次上线时，负载均衡选择会出问题
                    _machines[id].LoadReset();
                    break;
                }
            }
            _mutex.unlock();
        }

        void OnlineMachine()
        {
            //上线逻辑：所有主机都下线了，此时上线所有主机
            _mutex.lock();
            if(_offline.size())
            {
                _online.insert(_online.end(),_offline.begin(),_offline.end());
                _offline.clear();
            }
;
            _mutex.unlock();
        }
        
        //for test
        void ShowMachines()
        {
            _mutex.lock();
            std::string str_on;
            std::string str_off;
            for(auto &id : _online)
                str_on += std::to_string(id) + ' ';
            LOG(LogLevel::INFO) << "当前在线主机列表: " << str_on;
            for(auto &id : _offline)
                str_off += std::to_string(id) + ' ';
            LOG(LogLevel::INFO) << "当前离线主机列表: " << str_off;
            _mutex.unlock();
        }
    };

    class Control
    {
        private:
        Model _model;//提供后台数据
        View  _view; //提供html渲染功能
        LoadBalance _lb;//核心负载均衡模块
        public:

        //获取所有题目信息，构建字符串，本质是html文本:首页题目列表信息

        //控制中用以恢复所有主机
        void RecoverAllMachines()
        {
            _lb.OnlineMachine();
        }
        bool AllQuestions(std::string& out)
        {
            //先拿到所有题目信息
            bool ret = true;
            std::vector<Question> questions;
            if(_model.GetAllQuestions(questions))
            {
                //题目本身使用哈希进行存储,不一定是有序的
                // std::sort(questions.begin(),questions.end(),[](const Question& q1,const Question& q2)
                // {
                //     return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
                // });

                //并不直接推荐上述排序方式,深拷贝太多,消耗过大

                //获取到所有题目信息,将这些数据转换为html:使用ctemplate--->通过view模块实现
                _view.AllExpandHtml(questions,out);
            }
            else
            {
                out = "获取题目失败,形成题目列表失败";
                ret = false;
            }
            return ret;
        }

        //获取单个题目信息，构建html文本：具体题目信息
        bool OneQuestion(std::string& out,std::string& number)
        {
            bool ret = true;
            Question q;
            //通过编号拿到一道题目的信息
            if(_model.GetOneQuestion(number,q))
            {
               _view.OneExpandHtml(q,out);
            }
            else
            {
                out = "指定题目" + number + "不存在";
                ret = false;
            }
            return ret;
        }

        bool HomePage(std::string& out)
        {
            bool ret = true;
            if(!_view.GetHomePage(out))
            {
                ret = false;
                out = "获取OJ首页失败";
            }
            return ret;
        }


        //用户提交过来的json串:code(用户的编辑代码)和input(用户提交的输入,忽略)
        void Judge(const std::string &number,const std::string &in_json,std::string &out_json)
        {
            //1.通过题目编号,拿到具体题目细节
            struct Question q;
            _model.GetOneQuestion(number,q);


            //2.in_json反序列化
            Json::Reader reader;
            Json::Value in_value;
            reader.parse(in_json,in_value);  
            std::string code = in_value["code"].asString();//用户提交的代码
            
            //3.重新拼接用户代码+测试用例代码-->形成新代码,同时完成序列化json串
            Json::Value compile_value;
            compile_value["input"] = in_value["input"].asString();
            
            //区分接口型题目和I/O型题目
            compile_value["mode"] = q.mode;
            if(compile_value["mode"].asString() == "Interface")
                compile_value["code"] = code + "\n" + q.tail;//接口型拼接,IO不拼接
            else
                compile_value["code"] = code;
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            if(compile_value["mode"].asString() == "IO")
            {
                compile_value["IO_Input"] = q.IO_Input_File;
                compile_value["IO_Output"] = q.IO_Output_File;
            }
            Json::StyledWriter writer;
            std::string compile_json = writer.write(compile_value);//拿到发送给后端compile_and_run模块的json串


            //4.选择负载最低的主机(差错处理)
            //如何进行差错处理：一直选择，直到有主机可用,发送请求成功--->否则，就是全部挂掉
            while(true)
            {
                // LOG(DEBUG) << "进入循环" << std::endl;
                int id = 0;
                Machine* m;
                if(!_lb.SmartChoice(id,&m))
                {
                    //否则全部挂掉
                    break;
                }
                LOG(LogLevel::INFO) << "选择主机成功,主机id: " << id << " 详情: " << m->_ip << ":" << m->_port  << " 当前主机的负载数:" << m->_load;
                
                //选择主机成功,发起http请求:作为客户端
                Client client(m->_ip,m->_port);
                m->LoadIncrease();//发起请求,增加相应负载
                if(auto res = client.Post("/compile_and_run",compile_json,"application/json;charset=utf-8"))
                {
                    //拿到编译之后的结果
                    if(res->status == 201)
                    {
                        out_json = res->body;
                        m->LoadDecrease();//请求处理完毕,减少相应负载
                        LOG(LogLevel::INFO) << "请求和编译运行服务成功";
                        break;
                    }
                    m->LoadDecrease();
                }
                else
                {
                    //请求失败
                    LOG(LogLevel::ERROR) << "当前请求主机的id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 可能已经离线";
                    m->LoadDecrease();//减少相应负载
                    _lb.OfflineMachine(id);
                    _lb.ShowMachines();
                }
            }
        }     
    };
}